using System;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;

namespace Weever.Json.Serialization.v1
{
    public class JsonWriter: IJsonWriter
    {
        private char _quotationChar;
        private bool _quotateNames;
        private TextWriter _writer;

        private const string JsonNull = "null";
        private const string JsonTrue = "true";
        private const string JsonFalse = "false";

        private static readonly NumberFormatInfo NumberFormat = NumberFormatInfo.InvariantInfo;

        internal JsonWriter(TextWriter writer)
        {
            _quotationChar = '\"';
            _quotateNames = false;
            _writer = writer;
        }

        public char QuotationChar
        {
            get { return _quotationChar; }
            set { _quotationChar = value; }
        }

        public bool QuotateNames
        {
            get { return _quotateNames; }
            set { _quotateNames = value; }
        }

        public void WriteName(string name)
        {
            if (_quotateNames)
                _writer.Write("{0}{1}{0}:", QuotationChar, name);
            else
                _writer.Write(name + ":");
        }

        public void Write(int? value)
        {
            Write<int>(value);
        }

        public void Write(float? value)
        {
            Write<float>(value);
        }

        public void Write(double? value)
        {
            Write<double>(value);
        }

        public void Write(bool? value)
        {
            if (value == null)
                WriteNull();
            else if (value.Value)
                _writer.Write(JsonTrue);
            else 
                _writer.Write(JsonFalse);
        }

        public void WriteNull()
        {
            _writer.Write(JsonNull);
        }

        public void WriteBeginObject()
        {
            _writer.Write("{");
        }

        public void WriteEndObject()
        {
            _writer.Write("}");
        }

        public void WriteEndProperty()
        {
            _writer.Write(",");
        }

        protected void Write<T>(T value)
            where T: struct
        {
            _writer.Write(string.Format(NumberFormat, "{0}", value));
        }

        protected void Write<T>(T ? value)
            where T: struct
        {
            if (value == null)
                WriteNull();
            else
                _writer.Write(string.Format(NumberFormat, "{0}", value.Value));
        }

        public void Write(string value)
        {
            if (value == null)
                WriteNull();
            else if (value.Length == 0)
                _writer.Write("{0}{0}", QuotationChar);
            else
                _writer.Write("{0}{1}{0}", QuotationChar, GetEscapedString(value));
                
        }

        private static readonly Regex EscapeRegex =
            new Regex(@"(?<toEscape>[^\x20-\x21\x23-\x26\x28-\x5B\x5D-\u10FFFF])", RegexOptions.Compiled);

        public static string GetEscapedString(string value)
        {
            return EscapeRegex.Replace(value, EscapeMatchEvaluator);
        }

        private static string EscapeMatchEvaluator(Match match)
        {
            Group group = match.Groups[0];
            switch (group.Value)
            {
                    // two-character sequence escape representations
                case "\"": 
                    return "\\\""; // "    quotation mark  U+0022
                case "'":
                    return "\\'";  // '
                case "\\":
                    return "\\\\"; // \    reverse solidus U+005C
                case "\b":
                    return "\\b"; // b    backspace       U+0008
                case "\f":
                    return "\\f"; // f    form feed       U+000C
                case "\n":
                    return "\\n"; // n    line feed       U+000A
                case "\r":
                    return "\\r"; // r    carriage return U+000D
                case "\t":
                    return "\\t"; // t    tab             U+0009
                    // unicodes                  uXXXX                U+XXXX
                default:
                    return string.Format("\\u{0:X4}", (int) group.Value[0]);
            }
        }


        public void WriteBeginArray()
        {
            _writer.Write("[");
        }

        public void WriteEndArray()
        {
            _writer.Write("]");
        }
    }
}
